/* * Copyright 2008 The Apache Software Foundation or its licensors, as * applicable. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A licence was granted to the ASF by Florian Sager on 30 November 2008 */ package de.agitos.dkim; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import sun.misc.BASE64Encoder; import com.sun.mail.util.QPEncoderStream; /* * @author Florian Sager, http://www.agitos.de, 22.11.2008 */ public class DKIMUtil { protected static String[] splitHeader(String header) throws DKIMSignerException { int colonPos = header.indexOf(':'); if (colonPos==-1) { throw new DKIMSignerException("The header string "+header+" is no valid RFC 822 header-line"); } return new String[]{header.substring(0, colonPos), header.substring(colonPos+1)}; } protected static String concatArray(ArrayList l, String separator) { StringBuffer buf = new StringBuffer(); Iterator iter = l.iterator(); while (iter.hasNext()) { buf.append(iter.next()).append(separator); } return buf.substring(0, buf.length() - separator.length()); } protected static boolean isValidDomain(String domainname) { Pattern pattern = Pattern.compile("(.+)\\.(.+)"); Matcher matcher = pattern.matcher(domainname); return matcher.matches(); } // FSTODO: converts to "platforms default encoding" might be wrong ? protected static String QuotedPrintable(String s) { try { ByteArrayOutputStream boas = new ByteArrayOutputStream(); QPEncoderStream encodeStream = new QPEncoderStream(boas); encodeStream.write(s.getBytes()); String encoded = boas.toString(); encoded = encoded.replaceAll(";", "=3B"); encoded = encoded.replaceAll(" ", "=20"); return encoded; } catch (IOException ioe) {} return null; } protected static String base64Encode(byte[] b) { BASE64Encoder base64Enc = new BASE64Encoder(); String encoded = base64Enc.encode(b); // remove unnecessary linefeeds after 76 characters encoded = encoded.replace("\n", ""); // Linux+Win return encoded.replace("\r", ""); // Win --> FSTODO: select Encoder without line termination } public boolean checkDNSForPublickey(String signingDomain, String selector) throws DKIMSignerException { Hashtable<String, String> env = new Hashtable<String, String>(); env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); String recordname = selector+"._domainkey."+signingDomain; String value = null; try { DirContext dnsContext = new InitialDirContext(env); javax.naming.directory.Attributes attribs = dnsContext.getAttributes(recordname, new String[] {"TXT"}); javax.naming.directory.Attribute txtrecord = attribs.get("txt"); if (txtrecord == null) { throw new DKIMSignerException("There is no TXT record available for "+recordname); } // "v=DKIM1; g=*; k=rsa; p=MIGfMA0G ..." value = (String) txtrecord.get(); } catch (NamingException ne) { throw new DKIMSignerException("Selector lookup failed", ne); } if (value == null) { throw new DKIMSignerException("Value of RR "+recordname+" couldn't be retrieved"); } // try to read public key from RR String[] tags = value.split(";"); for (String tag : tags) { tag = tag.trim(); if (tag.startsWith("p=")) { try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); // decode public key, FSTODO: convert to DER format PKCS8EncodedKeySpec pubSpec = new PKCS8EncodedKeySpec(tag.substring(2).getBytes()); RSAPrivateKey pubKey = (RSAPrivateKey) keyFactory.generatePublic(pubSpec); } catch (NoSuchAlgorithmException nsae) { throw new DKIMSignerException("RSA algorithm not found by JVM"); } catch (InvalidKeySpecException ikse) { throw new DKIMSignerException("The public key "+tag+" in RR "+recordname+" couldn't be decoded."); } // FSTODO: create test signature with privKey and test validation with pubKey to check on a valid key pair return true; } } throw new DKIMSignerException("No public key available in "+recordname); } }